#pragma once//防止头文件重复引用
#include<iostream>
#include<vector>
#include<time.h>
using std::cout;//避免命名污染
using std::endl;
#ifdef _WIN32//判断是否是windows系统
#include<windows.h>
#else
// 
#endif

//定长内存池
//性能达到极致，不考虑内存碎片问题

#define PAGE_SHIFT 13 //页大小转换偏移，一页定义为2^13，也就是8kb
#define NUM 128*1024//一次向堆申请内存的空间大小128kb

//从堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	//调用操作系统接口
	void* ptr = VirtualAlloc(0, kpage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}
//释放内存回堆（不能自动回收资源，进程结束后对应进程地址空间都会被销毁）
inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
    VirtualFree(ptr, 0, MEM_RELEASE);
#else
    //linux下sbrk ummap等
#endif
}

//template<size_t N>//非类型模板参数，定长为N: 即申请大小都为N
//class ObjectPool
//{};
template<class T> //类型模板参数，T大小
class ObjectPool
{
public:
	T* New()
	{
		T* obj = nullptr;
		//优先把还回来内存块对象，再次重复利用
		if (_freeList)
		{
			void* next = *((void**)_freeList);//保留下一块内存的地址
			obj = (T*)_freeList;//obj需要强转一下，否则编译错误，强转是因为后续会解引用
			_freeList = next;//自由链表头指针移到下一块内存
		}
		else
		{
			//剩余内存不够一个对象大小时，则重新开大块空间
			if (_remainBytes < sizeof(T))
			{
				_remainBytes = NUM;
				//_memory = (char*)malloc(_remainBytes);//byte
                _memory = (char*)SystemAlloc(_remainBytes >> PAGE_SHIFT);//page
				if (_memory == nullptr)
				{
					throw std::bad_alloc();//申请失败抛出异常
				}
			}

			obj = (T*)_memory;//将申请到的空间划分T大小字节给obj指针
			//如果T为char或int且64位环境下，每一块的内存都不足以放下一块内存地址，所以给内存时至少给一个sizeof(void*)大小
			size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += objSize;//头指针移动T大小到未使用部分
			_remainBytes -= objSize;//剩余保留内存大小
		}
		// 定位new, 显示调用T的构造函数初始化
		new(obj)T;

		return obj;
	}

	void Delete(T* obj)
	{
		//显示调用析构函数清理对象
		obj->~T();

		//头插，将obj强转为二级指针，指向一级指针大小，对其解引用从而获取指向下一块内存地址的指针
		*(void**)obj = _freeList;
		_freeList = obj;
	}
private:
	char* _memory = nullptr;//内存池的头指针
	size_t _remainBytes = 0;//内存池未使用部分的大小
	void* _freeList = nullptr;//还回来过程中链接的自由链表的头指针
};




